package cn.learn.server.persistence;

import cn.learn.server.common.AtomicFileOutputStream;

import java.io.*;
import java.nio.ByteBuffer;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.GZIPInputStream;

public class SnapStream {

    public static final String ZOOKEEPER_SHAPSHOT_STREAM_MODE = "zookeeper.snapshot.compression.method";

    private static StreamMode streamMode = StreamMode.fromString(
            System.getProperty(ZOOKEEPER_SHAPSHOT_STREAM_MODE,
                    StreamMode.DEFAULT_MODE.getName()));

    public static StreamMode getStreamMode() {
        return streamMode;
    }

    public static StreamMode getStreamMode(String fileName) {
        String[] splitSnapName = fileName.split("\\.");
        // Use file extension to detect format
        if (splitSnapName.length > 1) {
            String mode = splitSnapName[splitSnapName.length - 1];
            return StreamMode.fromString(mode);
        }
        return StreamMode.CHECKED;
    }

    public static CheckedInputStream getInputStream(File file) throws IOException {
        FileInputStream fis = new FileInputStream(file);
        InputStream is;
        try {
            switch (getStreamMode(file.getName())) {
                case CHECKED:
                default:
                    is = new BufferedInputStream(fis);
            }
            return new CheckedInputStream(is, new Adler32());
        } catch (Exception e) {
            fis.close();
            throw e;
        }
    }

    public static CheckedOutputStream getOutputStream(File file, boolean fsync) throws IOException {
        OutputStream fos = fsync ? new AtomicFileOutputStream(file) : new FileOutputStream(file);
        OutputStream os;
        switch (streamMode) {
            case CHECKED:
            default:
                os = new BufferedOutputStream(fos);
        }
        return new CheckedOutputStream(os, new Adler32());
    }

    public static boolean isValidSnapshot(File file) throws IOException{
        if (file == null || Util.getZxidFromName(file.getName(), FileSnap.SNAPSHOT_FILE_PREFIX) == -1) {
            return false;
        }
        boolean isValid = false;
        switch (getStreamMode(file.getName())) {
            case CHECKED:
            default:
                isValid = isValidCheckedStream(file);
        }
        return isValid;
    }

    private static boolean isValidCheckedStream(File f) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(f, "r")) {
            if (raf.length() < 10) {
                return false;
            }

            raf.seek(raf.length() - 5);
            byte[] bytes = new byte[5];
            int readlen = 0;
            int l;
            while (readlen < 5 && (l = raf.read(bytes, readlen, bytes.length - readlen)) >= 0) {
                readlen += l;
            }
            if (readlen != bytes.length) {
                return false;
            }
            ByteBuffer bb = ByteBuffer.wrap(bytes);
            int len = bb.getInt();
            byte b = bb.get();
            if (len != 1 || b != '/') {
                return false;
            }
        }
        return true;
    }

    public enum StreamMode {
        GZIP("gz"),
        SNAPPY("snappy"),
        CHECKED("");

        public static final StreamMode DEFAULT_MODE = CHECKED;

        private String name;

        StreamMode(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public String getFileExtension() {
            return name.isEmpty() ? "" : "." + name;
        }

        public static StreamMode fromString(String name) {
            for (StreamMode c : values()) {
                if (c.getName().compareToIgnoreCase(name) == 0) {
                    return c;
                }
            }
            return DEFAULT_MODE;
        }
    }
}
